﻿namespace DateControls
{
    using System;
    using System.ComponentModel;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Web.UI.WebControls;
    using AjaxControlToolkit;

    /// <summary>
    /// Control to allow to enter dates on a text box.
    /// </summary>
    public class DateTextBox : TextBox
    {
        /// <summary>
        /// the location of the jquery js file. 
        /// </summary>
        private const string JQUERY_SCRIPT_RESOURCE = "DateControls.Resources.jquery.min.js";

        private const string DATE_MASK = "99/99/9999";
        private const string DATE_REGEXP = "^([1-9]|0[1-9]|[12][0-9]|3[01])[ /.]([1-9]|0[1-9]|1[012])[ /.][1-2][0-9]{3}";
        
        /// <summary>
        /// Gets or sets the masked edit extender.
        /// </summary>
        /// <value>The masked edit extender.</value>
        private MaskedEditExtender MaskedEditExtender { get; set; }
     
        #region Public Properties

        /// <summary>
        /// Gets or sets a value indicating whether the date is required.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if the date is required; otherwise, <c>false</c>.
        /// </value>
        [Category("Behavior"), DefaultValue(false)]
        [Description("Gets or sets a value indicating whether the date is required.")]
        public bool IsValidEmpty { get; set; }

        /// <summary>
        /// Gets or sets the error CSS class.
        /// </summary>
        /// <value>The error CSS class.</value>
        [Category("Layout")]
        [Description("Gets or sets the error CSS class.")]
        public string ErrorCSSClass { get; set; }

        /// <summary>
        /// True to show a tooltip message when the mouse hovers over an invalid TextBox.
        /// </summary>
        [Category("Layout")]
        [Description("True to show a tooltip message when the mouse hovers over an invalid TextBox.")]
        public bool ErrorTooltipEnabled
        {
            get
            {
                this.EnsureChildControls();
                return this.MaskedEditExtender.ErrorTooltipEnabled;
            }

            set
            {
                this.EnsureChildControls();
                this.MaskedEditExtender.ErrorTooltipEnabled = value;
            }
        }

        /// <summary>
        /// CSS class for the tooltip message.
        /// </summary>
        [Category("Layout")]
        [Description("CSS class for the tooltip message.")]
        public string ErrorTooltipCssClass
        {
            get
            {
                this.EnsureChildControls();
                return this.MaskedEditExtender.ErrorTooltipCssClass;
            }
            set
            {
                this.EnsureChildControls();
                this.MaskedEditExtender.ErrorTooltipCssClass = value;
            }
        }

        /// <summary>
        /// Message displayed when editing in TextBox.
        /// </summary>
        [Category("Layout")]
        [Description("Message displayed when editing in TextBox.")]
        public bool MessageValidatorTip
        {
            get
            {
                this.EnsureChildControls();
                return this.MaskedEditExtender.MessageValidatorTip;
            }
            set
            {
                this.EnsureChildControls();
                this.MaskedEditExtender.MessageValidatorTip = value;
            }
        }

        /// <summary>
        /// Determines whether the value of the control is valid.
        /// </summary>
        /// <returns>
        /// 	<c>true</c> if this instance is valid; otherwise, <c>false</c>.
        /// </returns>
        [Category("Behavior")]
        [Description("Determines whether the value of the control is valid.")]
        public bool IsValid
        {
            get
            {
                DateTime date;
                // first we validate if the entered text is valid
                if (!string.IsNullOrEmpty(this.Text) && DateTime.TryParse(this.Text, out date))
                {
                    return true;
                }

                // if cant be empty and the text is empty
                if (!this.IsValidEmpty && string.IsNullOrEmpty(this.Text))
                {
                    return false;
                }

                // if can be empty and the text is empty
                if (this.IsValidEmpty && string.IsNullOrEmpty(this.Text))
                {
                    return true;
                }

                // if can be empty and the expression match
                if (this.IsValidEmpty && this.ValidationExpression.Match(this.Text).Success)
                {
                    return true;
                }

                // if cant be empty and the expression match
                if (!this.IsValidEmpty && this.ValidationExpression.Match(this.Text).Success)
                {
                    return true;
                }
                else return false;
            }
        }

        /// <summary>
        /// Gets or sets the value of the control.
        /// </summary>
        /// <returns>The DateTime of the control or throws InvalidOperationException 
        /// trying to get a value from the control when is in invalid state.</returns>
        [Category("Behavior")]
        [Description("Gets or sets the value of the control.")]
        public DateTime? Value
        {
            get
            {
                if (this.IsValid)
                {
                    // first we need to check if the control is valid and the text is not empty
                    if (!string.IsNullOrEmpty(this.Text))
                    {
                        return DateTime.Parse(this.Text);
                    }   
                    else
                    {
                        // when the control accept nulls, and the text is empty
                        return null;
                    }
                }
                else throw new InvalidOperationException("Cannot get the value when is in invalid state.");
            }

            set
            {
                this.Text = (value != null) ? value.ToString().Substring(0, this.Mask.Length) : null;
            }
        }

        #endregion

        /// <summary>
        /// Gets or sets the type of the mask.
        /// </summary>
        /// <value>The type of the mask.</value>
        protected virtual MaskedEditType MaskType
        {
            get { return MaskedEditType.Date; }
        }

        /// <summary>
        /// Gets or sets the mask.
        /// </summary>
        /// <value>The mask.</value>
        protected virtual string Mask
        {
            get { return DATE_MASK; }
        }

        /// <summary>
        /// Gets the validation expression.
        /// </summary>
        /// <value>The validation expression.</value>
        protected virtual Regex ValidationExpression
        {
            get
            {
                return new Regex(DATE_REGEXP);
            }
        }

        /// <summary>
        /// Sets the type of the extender mask.
        /// </summary>
        protected void SetExtenderMaskType()
        {
            this.EnsureChildControls();
            this.MaskedEditExtender.MaskType = this.MaskType;
        }

        /// <summary>
        /// Sets the extender mask.
        /// </summary>
        protected void SetExtenderMask()
        {
            this.EnsureChildControls();
            this.MaskedEditExtender.Mask = this.Mask;
        }

        protected override void OnInit(System.EventArgs e)
        {
            this.RegisterJquery();
            base.OnInit(e);
        }
        
        protected override void CreateChildControls()
        {
            // Creates and setup the Masked Edit Extender
            this.MaskedEditExtender =  this.CreateAndSetMaskeEdit();

            this.SetExtenderMaskType();
            this.SetExtenderMask();

            this.Controls.Add(this.MaskedEditExtender);
        }

        private MaskedEditExtender CreateAndSetMaskeEdit()
        {
            var masked = new MaskedEditExtender();
            masked.TargetControlID = this.ID;
            masked.AcceptNegative = MaskedEditShowSymbol.None;
            masked.UserDateFormat = MaskedEditUserDateFormat.DayMonthYear;
            masked.UserTimeFormat = MaskedEditUserTimeFormat.TwentyFourHour;
            masked.ID = this.ID + "_MaskedEditExtender";

            return masked;
        }

        /// <summary>
        /// Gets the additional jquery scripts(used for customization).
        /// </summary>
        /// <returns></returns>
        protected virtual string GetAdditionalJqueryScripts()
        {
            return string.Empty;
        }
        
        protected override void Render(System.Web.UI.HtmlTextWriter writer)
        {
            base.Render(writer);
            this.MaskedEditExtender.RenderControl(writer);

            // if is enabled
            if (this.Enabled)
            {
                // renders the jquery script.
                writer.Write(this.GetJqueryScript());
            }
        }

        /// <summary>
        /// Sets the js validation block to the String Builder object.
        /// </summary>
        /// <param name="sb">The string builder object where the validation function will be appended.</param>
        /// <param name="dateToValidate">The date to be inserted in the validation block.</param>
        protected void SetJsValidationBlock(StringBuilder sb, string dateToValidate)
        {
            sb.AppendFormat("         if (/{0}/.test({1}))", string.Format("{0}{1}", this.ValidationExpression.ToString(), "$"),dateToValidate);
            sb.Append("               {");
            sb.AppendFormat("              if ($(this).hasClass(\"{0}\"))", this.ErrorCSSClass);
            sb.Append("                    {");
            sb.AppendFormat("                 $(this).removeClass(\"{0}\");", this.ErrorCSSClass);
            sb.Append("                    }");
            sb.Append("                }");
            sb.Append("                else {");
            sb.AppendFormat("                $(this).addClass(\"{0}\");", this.ErrorCSSClass);
            sb.Append("               }");
        }

        /// <summary>
        /// Registers the jquery library.
        /// </summary>
        private void RegisterJquery()
        {
            string jqueryResourceUrl = this.Page.ClientScript.GetWebResourceUrl(this.GetType(), JQUERY_SCRIPT_RESOURCE);
            
            this.Page.ClientScript.RegisterClientScriptInclude(this.GetType(), "jquery", 
                                                               jqueryResourceUrl);
        }

        /// <summary>
        /// Gets the jquery script.
        /// </summary>
        /// <returns></returns>
        private string GetJqueryScript()
        {
            var sb = new StringBuilder();

            sb.Append("<script type=\"text/javascript\">");
            sb.Append("$(document).ready(function() {");
            sb.AppendFormat("   $(\"#{0}\").blur(function()", this.ClientID);
            sb.Append("         {");
            sb.Append("            var date = $(this).val();");

            if (this.IsValidEmpty)
            {
                sb.Append("        if (date.length != 0) {");
            }

            // set the validation block using the value the date variable
            this.SetJsValidationBlock(sb,"date");

            if (this.IsValidEmpty)
            {
                sb.Append("         } else {");
                sb.AppendFormat("          if ($(this).hasClass(\"{0}\"))", this.ErrorCSSClass);
                sb.Append("                {");
                sb.AppendFormat("             $(this).removeClass(\"{0}\");", this.ErrorCSSClass);
                sb.Append("                }");
                sb.Append("         }");
            }

            sb.Append("          });");

            sb.Append(this.GetAdditionalJqueryScripts());

            sb.Append("});");

            if (!this.IsValid)
            {
                sb.AppendFormat("$(\"#{0}\").addClass(\"{1}\");", this.ClientID, this.ErrorCSSClass);
            }

            sb.Append("</script>");
            return sb.ToString();
        }
    }
}